﻿/*
 *	Copyright (c) 2009 Queensland University of Technology. All rights reserved.
 *	The QUT Bioinformatics Collection is open source software released under the 
 *	Microsoft Public License (Ms-PL): http://www.microsoft.com/opensource/licenses.mspx.
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using QUT.Bio.SilverMap.ObjectModel;
using QUT.Bio.Util;
using QUT.Bio.Map2D;
using System.IO;
using QUT.Bio.Graphs;

#if ! SILVERLIGHT
using WinForms = System.Windows.Forms;
#endif

namespace QUT.Bio.SilverMap {
	/// <summary>
	/// A MapControl encapsulates a 2-d map.
	/// </summary>

	public partial class MapControl : UserControl {

		protected ViewModel model;
		protected PopupOverlay overlay;
		private IPageLayout pageLayout;
		private View view;
		private HitStack alignment;
		private SequenceSummaryTable sequenceSummaryTable;
		private SequencePropertyPanel sequencePropertyPanel;
		private HistoryPanel historyPanel;
		private SearchControl searchControl;
		private Action<ILinearDomain> navigationCallback;

		/// <summary> Set up the model, data layer and view for the map.
		/// </summary>

		public MapControl () {
			InitializeComponent();
		}

		/// <summary> Initializes a Mapcontrol, defining the graph adapter that maps the
		///	problem domain to the graph framework and supplying delegates that
		///	will respond to "Show sequence details" and "Navigate to sequence" events.
		/// </summary>
		/// <param name="overlay">A PopupOverlay that is used to display alerts to the user in a portable manner.</param>
		/// <param name="xrefCallback">A delegate that responds to the "Show sequence details" user gesture.</param>
		/// <param name="navigationCallback">A delegate that can do custom actions when the map navigates to centre on a sequence. This will be called as part of the navigation procedure, so long-running tasks should be hived off to a background thread.</param>
		/// <param name="adapter">A graph adapter that defines a mapping from an application domain to a graph model.</param>
		/// <param name="pageLayout">A layout control in which the elements of the display are organised.</param>

		public void Initialize (
			PopupOverlay overlay,
			Action<SequencePropertyPanel, ILinearDomain, IEnumerable<Xref>> xrefCallback = null,
			Action<ILinearDomain> navigationCallback = null,
			IGraphAdapter<SequenceSummary> adapter = null,
			IPageLayout pageLayout = null
		) {
			if ( adapter == null ) adapter = new DefaultSilverMapAdapter(
				map: this,
				overlay: overlay,
				epsilon: 0.1,
				sortPropertyNames: new string[] { "Name", "DistanceFromReferenceSequence" }
			);

			if ( pageLayout == null ) pageLayout = new HorizontalLayout2();

			this.overlay = overlay;
			this.navigationCallback = navigationCallback;
			this.pageLayout = pageLayout;

			mapContainer.Children.Clear();
			mapContainer.Children.Add( (UIElement) this.pageLayout );

			model = new ViewModel();
			view = pageLayout.View;
			alignment = pageLayout.Alignment;
			sequenceSummaryTable = pageLayout.SequenceSummaryTable;
			sequencePropertyPanel = pageLayout.SequencePropertyPanel;
			historyPanel = pageLayout.HistoryPanel;
			searchControl = pageLayout.SearchControl;

			view.InitChildControls( model );
			view.GraphView.Adapter = adapter;

			// view.GraphView.Canvas.Background = (Brush) Resources["BG_COLOR_BRUSH"];

			alignment.Initialise( this );
			sequenceSummaryTable.Initialize( this );
			sequencePropertyPanel.Initialize( model, xrefCallback );
			historyPanel.Initialise( model );
			// TODO: add search control initialisation.
		}

		#region Method: dataProducer_LoadComplete
		/// <summary>
		///	Invoked in response to a successful user-instigated (from the toolbar) load.
		///	<para>Plugs the completing data producer into the model, which then does 
		///	further processing to rebuild the map displays.</para>
		/// </summary>
		/// <param name="source">A data producer that has successfully completed a load.</param>

		void dataProducer_LoadComplete ( DataProvider source ) {
			source.LoadComplete -= dataProducer_LoadComplete;
			model.DataProducer = source;
		}
		#endregion

		#region Property: Model
		/// <summary>
		/// Gets a reference to the model.
		/// </summary>

		public ViewModel Model {
			get {
				return model;
			}
		}
		#endregion

		#region Property: View
		/// <summary>
		/// Gets a reference to the View
		/// </summary>

		public View View {
			get {
				return view;
			}
		}
		#endregion

		#region Property: PageLayout
		/// <summary>
		/// Gets a reference to the current page layout, which is a slightly general
		/// kind of hard-coded thing. No, I am not satisfied with this descrtiption either.
		/// </summary>

		public IPageLayout PageLayout {
			get {
				return pageLayout;
			}
		}
		#endregion

		#region Method NavigateToSequence
		/// <summary>
		/// Causes the map to visibly move to the specified sequence. Also, the sequence is 
		/// inserted into the sequence gene display and sequence hit display, so flipping to 
		/// those tabs will show us the nominated gene.
		/// </summary>
		/// <param name="sequence">The sequence that we want to see.</param>

		public void NavigateToSequence ( ILinearDomain sequence ) {
			// TODO: Fix the SIlverlight animation -- use DoubleAnimationUsingKeyFrames to control the scale with a single animation. See http://forums.silverlight.net/forums/t/47741.aspx

			sequencePropertyPanel.Sequence = sequence;
			alignment.SelectedSequence = sequence;
			pageLayout.Show( sequencePropertyPanel );
			var nodeView = view.GraphView.NodeViews[model.SequenceSummary[sequence]];
			double x = nodeView.PositionedElement.X;
			double y = nodeView.PositionedElement.Y;
			double zoomOutScale = 0.8;
			double zoomInScale = view.GraphView.Canvas.MaximumScale / 2;
			// Debug.WriteLine( String.Format( "{0}, {1}", x, y ) );
			PositionInterpolator interpolator = new PositionInterpolator( view.GraphView.Canvas, -x, y );
			Storyboard b = new Storyboard();

			Timeline zoom = GraphView<SequenceSummary>.CreateTimeLine(
				view.GraphView.Canvas,
				Map2DCanvas.ScaleProperty,
				new Triple<double, double, TimeSpan> {
					First = zoomOutScale,
					Second = 0.5,
					Third = new TimeSpan( 0, 0, 0, 1, 0 )
				},
				new Triple<double, double, TimeSpan> {
					First = zoomOutScale,
					Second = 0.5,
					Third = new TimeSpan( 0, 0, 0, 2, 0 )
				},
				new Triple<double, double, TimeSpan> {
					First = zoomInScale,
					Second = 0.5,
					Third = new TimeSpan( 0, 0, 0, 3, 0 )
				}
			);

			Timeline move = GraphView<SequenceSummary>.CreateTimeLine(
				1,
				0.5,
				new TimeSpan( 0, 0, 0, 1, 0 ),
				interpolator,
				PositionInterpolator.TProperty
			);

			move.BeginTime = new TimeSpan( 0, 0, 0, 1, 0 );

			b.Children.Add( move );
			b.Children.Add( zoom );

#if ! SILVERLIGHT
			b.FillBehavior = FillBehavior.Stop;
			b.Completed += delegate( object sender, EventArgs args ) {
				view.GraphView.Canvas.Scale = zoomInScale;
				interpolator.T = 1;
			};
#endif

			b.Begin();

			if ( navigationCallback != null ) {
				navigationCallback( sequence );
			}
		}
		#endregion

		#region Property: CanvasBackground
		/// <summary>
		/// Get or set the colour of the canvas.
		/// </summary>

		public Brush CanvasBackground {
			get {
				return view.GraphView.Canvas.Background;
			}
			set {
				view.GraphView.Canvas.Background = value;
			}
		}
		#endregion
	}
}
